/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019.
 * Description: This file provides a list of hash values for each read-only VMA
 * Author: liuqixiao xiekunxun
 * Create: 2019-06-14
 */

#include <crypto/hash.h>
#include <crypto/sha2.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/pagemap.h>
#include <linux/sched/mm.h>
#include <linux/rtos_mm_types.h>
#include <linux/rtos_sysctl.h>
#include "internal.h"
#include "proc_rtos.h"

#ifdef CONFIG_RTOS_RO_VMA_SHA256
#define VMA_HASH_ALG	("sha256")
#define VMA_HASH_ALG_SIZE (SHA256_DIGEST_SIZE)
#endif
#ifdef CONFIG_RTOS_RO_VMA_SHA512
#define VMA_HASH_ALG	("sha512")
#define VMA_HASH_ALG_SIZE (SHA512_DIGEST_SIZE)
#endif

#define HASH_PAGE_NUM	(1)
#define SEQ_PAD		(1)
#define MAX_ERR_LEN	(31)

#define SPECICAL_VMA_VVAR	("[vvar]")

/* Default call threshold is ten minutes */
#define DEFAULT_THRESHOLD 600
/* Max call threshold is one hour */
#define MAX_THRESHOLD 3600
/* Min call threshold is one minute */
#define MIN_THRESHOLD 60
unsigned long ro_vma_hash_min_threshold = MIN_THRESHOLD;
unsigned long ro_vma_hash_max_threshold = MAX_THRESHOLD;
unsigned long __cacheline_aligned_in_smp ro_vma_hash_threshold
	= DEFAULT_THRESHOLD;

struct ro_vma_hash_private {
	struct proc_maps_private proc_maps;
	struct shash_desc *desc;
	u8 hash_data[VMA_HASH_ALG_SIZE];
	char err_buf[MAX_ERR_LEN + 1];
};

static int calc_vma_hash(struct ro_vma_hash_private *private, const struct vm_area_struct *vma)
{
	struct proc_maps_private *maps = &private->proc_maps;
	struct shash_desc *desc = private->desc;
	char *buf = private->err_buf;
	void *kaddr = NULL;
	unsigned long addr;
	int ret;

	ret = crypto_shash_init(desc);
	if (ret != 0) {
		snprintf(buf, MAX_ERR_LEN, "HASH_INIT_ERR:%d", ret);
		goto out;
	}

	for (addr = vma->vm_start; addr < vma->vm_end; addr += PAGE_SIZE) {
		struct page *page = NULL;

		ret = get_user_pages_remote(maps->mm, addr, HASH_PAGE_NUM,
				FOLL_GET, &page, NULL, NULL);
		if (ret <= 0) {
			snprintf(buf, MAX_ERR_LEN, "GET_PAGE_ERR:%d", ret);
			goto out;
		}

		kaddr = kmap(page);
		ret = crypto_shash_update(desc, kaddr, PAGE_SIZE);
		if (ret) {
			kunmap(page);
			put_page(page);
			snprintf(buf, MAX_ERR_LEN, "HASH_UPDATE_ERR:%d", ret);
			goto out;
		}
		kunmap(page);
		put_page(page);
	}

	ret = crypto_shash_final(desc, private->hash_data);
	if (ret != 0) {
		snprintf(buf, MAX_ERR_LEN, "HASH_FINAL_ERR:%d", ret);
		goto out;
	}

	snprintf(buf, MAX_ERR_LEN, "HASH_SUCCESS");
out:
	return ret;
}

static void print_vma_info(struct seq_file *seq_m, struct vm_area_struct *vma,
			u8 *hash_data, int hash_errors)
{
	struct ro_vma_hash_private *private = seq_m->private;
	struct file *file = vma->vm_file;
	int i;

	seq_setwidth(seq_m, VMA_HASH_ALG_SIZE * 2 + SEQ_PAD);
	if (hash_errors != 0) {
		seq_printf(seq_m, "%s", "NA");
	} else {
		for (i = 0; i < VMA_HASH_ALG_SIZE; i++)
			seq_printf(seq_m, "%02x", hash_data[i]);
	}
	seq_pad(seq_m, ' ');

	seq_setwidth(seq_m, MAX_ERR_LEN + SEQ_PAD);
	seq_printf(seq_m, "%s", private->err_buf);
	seq_pad(seq_m, ' ');

	if (file)
		seq_file_path(seq_m, file, "\n");

	seq_putc(seq_m, '\n');
}

/*
 * vvar ro-vma will be changed by kernel, so we need skip it.
 * See update_vsyscall in vdso.c.
 */
static bool is_ro_vma(struct vm_area_struct *vma)
{
	struct file *file = vma->vm_file;
	vm_flags_t flags = vma->vm_flags;
	const char *name = NULL;

	if (!(flags & VM_READ) || (flags & VM_WRITE))
		return false;

	if (file)
		return true;

	if (vma->vm_ops && vma->vm_ops->name) {
		name = vma->vm_ops->name(vma);
		if (name && strncmp(name, SPECICAL_VMA_VVAR, sizeof(SPECICAL_VMA_VVAR)) == 0)
			return false;
	}

	return true;
}

static void proc_ro_vma_hash_show(struct seq_file *seq_m,
			struct vm_area_struct *vma)
{
	struct ro_vma_hash_private *private = seq_m->private;
	int ret;

	if (!is_ro_vma(vma))
		return;

	ret = calc_vma_hash(private, vma);

	print_vma_info(seq_m, vma, private->hash_data, ret);
}

static int seq_ro_vma_hash_show(struct seq_file *m, void *v)
{
	proc_ro_vma_hash_show(m, v);

	/* always return 0 to show next vma */
	return 0;
}

static struct seq_operations proc_ro_vma_hash_op = {
	.show = seq_ro_vma_hash_show,
};

static int private_init_hash(struct ro_vma_hash_private *private)
{
	struct shash_desc *desc = NULL;
	struct crypto_shash *tfm = NULL;
	int ret;

	tfm = crypto_alloc_shash(VMA_HASH_ALG, 0, 0);
	if (IS_ERR(tfm)) {
		ret = PTR_ERR(tfm);
		goto alloc_shash_err;
	}

	desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL);
	if (!desc) {
		ret = -ENOMEM;
		goto alloc_desc_err;
	}

	desc->tfm = tfm;
	private->desc = desc;

	return 0;

alloc_desc_err:
	crypto_free_shash(tfm);
alloc_shash_err:
	return ret;
}

static void private_free_hash(const struct ro_vma_hash_private *private)
{
	struct crypto_shash *tfm = private->desc->tfm;

	kfree(private->desc);
	crypto_free_shash(tfm);
}

static int check_read_interval(struct file *file)
{
	struct task_struct *task = get_proc_task(file_inode(file));
	struct mm_struct *mm = NULL;
	struct rtos_mm_struct *rtos_mm = NULL;
	unsigned long expect;
	int ret = 0;

	if (!task)
		return -ESRCH;

	mm = get_task_mm(task);
	if (!mm) {
		ret = -ENOENT;
		goto err_no_mm;
	}

	rtos_mm = mm_to_rtos_mm(mm);
	down_write(&mm->mmap_lock);
	expect = rtos_mm->ro_vma_hash_jiffies + ro_vma_hash_threshold * HZ;
	if (time_before(jiffies, expect)) {
		ret = -EBUSY;
		goto err_time;
	}
	rtos_mm->ro_vma_hash_jiffies = jiffies;

err_time:
	up_write(&mm->mmap_lock);
	mmput(mm);
err_no_mm:
	put_task_struct(task);
	return ret;
}

static int ro_vma_hash_open(struct inode *inode, struct file *file)
{
	struct ro_vma_hash_private *private = NULL;
	struct seq_file *seq = NULL;
	int ret;

	ret = check_read_interval(file);
	if (ret < 0)
		return ret;

	/* Init private & seq queue */
	ret = proc_ro_vma_hash_open(inode, file, &proc_ro_vma_hash_op,
			sizeof(struct ro_vma_hash_private));
	if (ret != 0)
		goto proc_open_err;

	seq = file->private_data;
	private = seq->private;

	ret = private_init_hash(private);
	if (ret != 0)
		goto init_hash_err;

	return 0;

init_hash_err:
	proc_ro_vma_hash_release(inode, file);
proc_open_err:
	return ret;
}

static int ro_vma_hash_release(struct inode *inode, struct file *file)
{
	struct seq_file *seq = file->private_data;
	struct ro_vma_hash_private *private = seq->private;

	private_free_hash(private);
	return proc_ro_vma_hash_release(inode, file);
}

const struct file_operations proc_ro_vma_hash_operations = {
	.open		= ro_vma_hash_open,
	.read		= seq_read,
	.llseek		= no_llseek,
	.release	= ro_vma_hash_release,
};

static struct ctl_table rtos_ro_hash_sysctls[] = {
	{
		.procname       = "ro_vma_hash_threshold",
		.data           = &ro_vma_hash_threshold,
		.maxlen         = sizeof(unsigned long),
		.mode           = 0644,
		.proc_handler   = proc_doulongvec_minmax,
		.extra1         = &ro_vma_hash_min_threshold,
		.extra2         = &ro_vma_hash_max_threshold,
	},
	{}
};

rtos_debug_sysctls_proc_init(rtos_ro_hash, rtos_ro_hash_sysctls);
